﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using BlueCurve.AddinHostView;
using BlueCurve.Common;
using BlueCurve.Common.Application;


namespace WebCrawler
{
    class Program : BlueCurve.Common.InteractiveConsole.BaseConsole
    {
        #region 'Configuration'

        /// <summary>
        /// Permet au thread principal de continuer à tourner
        /// </summary>
        private static bool s_IsRun = true;
        /// <summary>
        /// Permet de savoir si le cache est en cours de mise à jour
        /// </summary>
        private static bool s_IsUpdating = false;
        /// <summary>
        /// Instance de la class de téléchargement
        /// </summary>
        private static Downloader s_Downloader { get; set; }
        /// <summary>
        /// Instance de la class de traitement
        /// </summary>
        private static ResponseTreat s_ResponseTreat = new ResponseTreat();
        /// <summary>
        /// Objet de vérouillage
        /// </summary>
        private static readonly object s_ThreadsLock = new object();
        /// <summary>
        /// Thread de traitement des 
        /// données téléchargés par le robot
        /// </summary>
        private static Thread s_Treatment { get; set; }
        /// <summary>
        /// Thread pour le rechargement de la file 
        /// de fichiers à télécharger
        /// </summary>
        private static Thread s_Queue { get; set; }
        /// <summary>
        /// Thread pour les données à ajouter au thread
        /// </summary>
        private static Thread s_Repository { get; set; }
        /// <summary>
        /// Thread pour la console interactive
        /// </summary>
        private static Thread s_Console { get; set; }

        #endregion




        #region 'Functions'

        /// <summary>
        /// Fonction principal de l'application
        /// </summary>
        /// <param name="argv">Ligne de commande</param>
        static void Main(string[] argv)
        {
            // Création du répertoire de sauvegarde
            if (!Directory.Exists(ConfigurationManager.AppSettings["DownloadedFilesDestination"]))
                Directory.CreateDirectory(ConfigurationManager.AppSettings["DownloadedFilesDestination"]);

            s_Downloader = new Downloader(s_ResponseTreat);
            s_ResponseTreat.AddFileToRepositoryCache += new ResponseTreat.AddFileinfoEventHandler(s_ResponseTreat_AddFileToRepositoryCache);
                        
            s_Console    = new Thread(new ThreadStart(new Program().Run));
            s_Treatment  = new Thread(new ThreadStart(s_ResponseTreat.TreatDownloadedData));
            s_Queue      = new Thread(new ThreadStart(ReloadQueue));
            s_Repository = new Thread(new ThreadStart(AddfileToRepository));
            s_Downloader.DeleteUriFromDatabaseEvent += new Downloader.DeleteUriFromDatabaseEventHandler(s_Downloader_DeleteUriFromDatabaseEvent);

            s_Treatment.Start();
            s_Queue.Start();
            s_Repository.Start();
            s_Console.Start();

            Tcpclient._RestartApplication += new EventHandler(TcpclientRestartApplication);
            Tcpclient._StopApplication += new EventHandler(TcpclientStopApplication);
             
            s_Downloader.RunDownloader(); // Démarrage du downloader

            if (s_Treatment.ThreadState == System.Threading.ThreadState.Running)
                s_Treatment.Abort();
            if (s_Queue.ThreadState == System.Threading.ThreadState.Running)
                s_Queue.Abort();
            if (s_Repository.ThreadState == System.Threading.ThreadState.Running)
                s_Repository.Abort();

            AddfileToRepository();
        }

        /// <summary>
        /// Recharge la file d'attente de données à télécharger
        /// </summary>
        private static void ReloadQueue()
        {
            while (s_IsRun)
            {
                using (WebCrawlerdbDataContext db = new WebCrawlerdbDataContext())
                {
                    if (db.Cache.Select(p => p.ID).Count() == 0)
                    {
                        s_IsUpdating = true;

                        // Client tcp
                        Tcpclient client = new Tcpclient();
                        // Demande de données au repository
                        string data = client.GetQueueForDownload();
                        if (!string.IsNullOrEmpty(data))
                        {
                            try
                            {
                                XDocument doc = XDocument.Load(new StringReader(data));
                                var x = from i in doc.Descendants("document")
                                        select new
                                        {
                                            Url = i.Element("url").Value,
                                            State = i.Element("state").Value,
                                            LastUpdate = i.Element("lastupdate").Value,
                                            LastModified = i.Element("lastmodified").Value,
                                            ReindexTime = i.Element("reindextime").Value,
                                            X509 = i.Element("X509").Value,
                                            X509V2 = i.Element("X509v2").Value,
                                            Options = i.Element("options").Value
                                        };

                                foreach (var v in x)
                                {
                                    db.AddDataToCrawlerCache(v.Url, (int?)int.Parse(v.State), (DateTime?)DateTime.Parse(v.LastUpdate),
                                                              (DateTime?)DateTime.Parse(v.LastModified), (int?)int.Parse(v.ReindexTime),
                                                              v.X509, v.X509V2, v.Options);

                                }
                            }
                            catch (Exception e)
                            {
                                Console.WriteLine(e);
                            }
                        }

                        s_IsUpdating = false;
                    }
                }

                if (s_IsRun)
                    Thread.Sleep(2 * 1000);
            }
        }


        /// <summary>
        /// Ajoute des fichiers à parser dans le repository
        /// </summary>
        private static void AddfileToRepository()
        {
            try
            {
                Tcpclient client = new Tcpclient();
                while (s_IsRun)
                {
                    using (WebCrawlerdbDataContext db = new WebCrawlerdbDataContext())
                    {
                        var x = (from v in db.RepositoryCache
                                 select new
                                 {
                                     Path = v.PATH,
                                     Typeoffile = v.TYPEOFFILE
                                 }).Take(150);

                        if (!s_IsRun) // pour finir le plus tot possible la fonction
                            break;
                        
                        foreach (var temp in x)
                        {
                            bool res = client.AddFileToRepository(temp.Typeoffile, temp.Typeoffile);
                            if (res)
                                db.DeleteFromRepositoryCache(temp.Path);
                            else
                                db.AddDataToRepositoryCache(temp.Path, temp.Typeoffile);
                        }
                    }

                    if (s_IsRun)
                        Thread.Sleep(750);
                }
            }
            catch(Exception e)
            {
                Console.WriteLine(e);
            }
        }
        
        #endregion




        #region 'Event'

        /// <summary>
        /// Fonction attachée à l'evenement suppression depuis la base de données
        /// </summary>
        private static void s_Downloader_DeleteUriFromDatabaseEvent(object sender, WebCrawler.Events.DeleteFromCrawlerCache e)
        {
            lock (s_ThreadsLock)
            {
                using (WebCrawlerdbDataContext db = new WebCrawlerdbDataContext())
                {
                    db.DeleteFromCrawlerCache(e.Uri);
                }
            }
        }


        /// <summary>
        /// Ajoute un fichier dans le cache du repository
        /// </summary>
        private static void s_ResponseTreat_AddFileToRepositoryCache(object sender, ResponseTreat.AddFileinfo e)
        {
            using (WebCrawlerdbDataContext db = new WebCrawlerdbDataContext())
            {
                db.AddDataToRepositoryCache(e.Uri, e.Typeoffile);
            }
        }
        
        /// <summary>
        /// Lecture et téléchargement des liens
        /// contenu dans l'annuaire dmoz
        /// </summary>
        /// <param name="path">Chemin d'accès à l'annuaire</param>
        private static void DmozDownload(string path)
        {
            if (!File.Exists(path))
                throw new Exception("Fichier introuvable");

            CommonFunctions common = new CommonFunctions();

            using (WebCrawlerdbDataContext db = new WebCrawlerdbDataContext())
            {
                foreach (string url in BlueCurve.Common.Dmoz.ReadDirectory(path))
                {
                    if (!string.IsNullOrEmpty(url))
                    {
                        db.AddDataToCrawlerCache(common.NormalizeLink(url), 1, null, null, 7, null, null, null);
                    }
                }
            }
        }
        
        /// <summary>
        /// Arrete l'application
        /// </summary>
        private static void TcpclientStopApplication(object sender, EventArgs e)
        {
            // Arret des threads
            _stop();

            // Suicide de l'application
            Process.GetCurrentProcess().Kill();
        }

        /// <summary>
        /// Relance l'application
        /// </summary>
        private static void TcpclientRestartApplication(object sender, EventArgs e)
        {
            // Arret des threads 
            _stop();

            // Enregistre l'application pour le redemarrage
            Restart.RestartApplication(string.Concat(System.Diagnostics.Process.GetCurrentProcess().ProcessName, ".exe"));
            // Suicide de l'application
            Process.GetCurrentProcess().Kill();
        }


        /// <summary>
        /// Arret des threads
        /// </summary>
        private static void _stop()
        {
            s_ResponseTreat.IsStop = false;
            while (s_ResponseTreat.AliveThreads > 0) { Thread.Sleep(100); }

            s_Downloader.IsStop = false;
            while (s_Downloader.AliveThreads > 0) { Thread.Sleep(100); }

            s_IsRun = false;
            while (s_IsUpdating) { Thread.Sleep(100); }
        }

        #endregion


        

        #region 'Console'

        /// <summary>
        /// Chargement des commandes
        /// </summary>
        protected override void LoadCommands()
        {
            this.Commands.Add("-stop", new Dictionary<Action, string>()
            { 
                { () => TcpclientStopApplication("", EventArgs.Empty), "Arrete l'application" }
            });
            this.Commands.Add("-restart", new Dictionary<Action, string>()
            {
                { () => TcpclientRestartApplication("", EventArgs.Empty), "Relance l'application" }
            });
            this.Commands.Add("-dmoz", new Dictionary<Action, string>()
            {
                { new Action(delegate { }), "Lance le téléchargement des liens ce trouvant dans l'annuaire dmoz fournit en paramètre" }
            });
        }

        /// <summary>
        /// Retourne le nom de l'application
        /// </summary>
        public override string Name
        {
            get { return "BlueCurve WebCrawler"; }
        }


        /// <summary>
        /// Activation des commandes
        /// </summary>
        /// <param name="cmd">Ligne de commande</param>
        protected override void ProcessCommand(string cmd)
        {
            if (cmd.StartsWith("-dmoz", StringComparison.InvariantCultureIgnoreCase))
            {
                DmozDownload(cmd.Split('"')[1]);
            }
            else
            {
                Dictionary<Action, string> action = null;
                if (Commands.TryGetValue(cmd, out action))
                {
                    foreach (KeyValuePair<Action, string> pair in action)
                        pair.Key();
                }
                else
                {
                    Console.WriteLine("Unknown command");
                }
            }
        }

        #endregion
    }
}